package edu.ncsu.csc.itrust.action;

import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.TreeSet;
import edu.ncsu.csc.itrust.action.base.OfficeVisitBaseAction;
import edu.ncsu.csc.itrust.beans.DiagnosisBean;
import edu.ncsu.csc.itrust.beans.Email;
import edu.ncsu.csc.itrust.beans.HospitalBean;
import edu.ncsu.csc.itrust.beans.InstructionBean;
import edu.ncsu.csc.itrust.beans.LabProcedureBean;
import edu.ncsu.csc.itrust.beans.LabTechProcedureBean;
import edu.ncsu.csc.itrust.beans.MedicationBean;
import edu.ncsu.csc.itrust.beans.MessageBean;
import edu.ncsu.csc.itrust.beans.OfficeVisitBean;
import edu.ncsu.csc.itrust.beans.OverrideReasonBean;
import edu.ncsu.csc.itrust.beans.PatientReferralBean;
import edu.ncsu.csc.itrust.beans.PersonnelBean;
import edu.ncsu.csc.itrust.beans.PrescriptionBean;
import edu.ncsu.csc.itrust.beans.PatientBean;
import edu.ncsu.csc.itrust.beans.DrugInteractionBean;
import edu.ncsu.csc.itrust.beans.AllergyBean;
import edu.ncsu.csc.itrust.beans.PrescriptionReportBean;
import edu.ncsu.csc.itrust.beans.ProcedureBean;
import edu.ncsu.csc.itrust.beans.forms.EditOfficeVisitForm;
import edu.ncsu.csc.itrust.dao.DAOFactory;
import edu.ncsu.csc.itrust.dao.mysql.AllergyDAO;
import edu.ncsu.csc.itrust.dao.mysql.DrugInteractionDAO;
import edu.ncsu.csc.itrust.dao.mysql.HospitalsDAO;
import edu.ncsu.csc.itrust.dao.mysql.LabProcedureDAO;
import edu.ncsu.csc.itrust.dao.mysql.NDCodesDAO;
import edu.ncsu.csc.itrust.dao.mysql.OfficeVisitDAO;
import edu.ncsu.csc.itrust.dao.mysql.OfficeVisitDiagnosisDAO;
import edu.ncsu.csc.itrust.dao.mysql.OfficeVisitInstructionDAO;
import edu.ncsu.csc.itrust.dao.mysql.OfficeVisitLabProcedureDAO;
import edu.ncsu.csc.itrust.dao.mysql.OfficeVisitMedicationDAO;
import edu.ncsu.csc.itrust.dao.mysql.OfficeVisitProcedureDAO;
import edu.ncsu.csc.itrust.dao.mysql.PatientDAO;
import edu.ncsu.csc.itrust.dao.mysql.PatientReferralDAO;
import edu.ncsu.csc.itrust.dao.mysql.PersonnelDAO;
import edu.ncsu.csc.itrust.dao.mysql.TransactionDAO;
import edu.ncsu.csc.itrust.exception.DBException;
import edu.ncsu.csc.itrust.exception.ErrorList;
import edu.ncsu.csc.itrust.exception.FormValidationException;
import edu.ncsu.csc.itrust.exception.iTrustException;
import edu.ncsu.csc.itrust.validate.EditOfficeVisitValidator;
import edu.ncsu.csc.itrust.validate.ValidationFormat;

/**
 * Edits the office visits of a patient Used by editOfficeVisit.jsp
 * 
 * @author laurenhayward
 * 
 */
public class EditOfficeVisitAction extends OfficeVisitBaseAction {
	private EditPrescriptionAction prescriptAct;
	private EditOfficeVisitValidator validator = new EditOfficeVisitValidator();
	private PersonnelDAO personnelDAO;
	private HospitalsDAO hospitalDAO;
	private OfficeVisitDAO ovDAO;
	private LabProcedureDAO lpDAO;
	private TransactionDAO transDAO;
	private PatientDAO patDAO;
	private PatientReferralDAO patRefDAO;
	private OfficeVisitDiagnosisDAO ovdDAO;
	private OfficeVisitLabProcedureDAO ovlpDAO;
	private OfficeVisitMedicationDAO ovmDAO;
	private OfficeVisitProcedureDAO ovpDAO;
	private OfficeVisitInstructionDAO oviDAO;
    private SendMessageAction MessageAction;
	private long loggedInMID;
	private long pid;

	/**
	 * Used to identify the subactions that can comprise an office visit action.
	 * Subactions help direct the action's logic because sometimes multiple 
	 * office visit actions can occur within one transaction.
	 */
	private enum OVSubAction {
		ADD_DIAGNOSIS,
		REMOVE_DIAGNOSIS,
		ADD_PROCEDURE,
		REMOVE_PROCEDURE,
		ADD_MEDICATION,
		REMOVE_MEDICATION,
		ADD_LAB_PROCEDURE,
		REMOVE_LAB_PROCEDURE,
		ADD_IMMUNIZATION,
		REMOVE_IMMUNIZATION,
		ADD_INSTRUCTION,
		EDIT_INSTRUCTION,
		REMOVE_INSTRUCTION,
		ADD_REFERRAL,
		EDIT_REFERRAL,
		REMOVE_REFERRAL
	};

	/**
	 * Patient id and office visit id validated by super class
	 * 
	 * @param factory The DAOFactory to be used in creating the DAOs for this action.
	 * @param loggedInMID The MID of the user who is authorizing this action.
	 * @param pidString The patient who this action is performed on.
	 * @param ovIDString The ID of the office visit in play.
	 * @throws iTrustException
	 */
	public EditOfficeVisitAction(DAOFactory factory, long loggedInMID, String pidString, String ovIDString)
			throws iTrustException {
		super(factory, pidString, ovIDString);
		pid = Long.parseLong(pidString);
		ovDAO = factory.getOfficeVisitDAO();
		ovdDAO = factory.getOVDiagnosisDAO();
		ovlpDAO = factory.getOVLabProcedureDAO();
		ovmDAO = factory.getOVMedicationDAO();
		ovpDAO = factory.getOVProcedureDAO();
		oviDAO = factory.getOVInstructionDAO();
		patRefDAO = factory.getPatientReferralsDAO();
		prescriptAct = new EditPrescriptionAction(factory);
		MessageAction = new SendMessageAction(factory,loggedInMID);
		this.personnelDAO = factory.getPersonnelDAO();
		this.hospitalDAO = factory.getHospitalsDAO();
		this.lpDAO = factory.getLabProcedureDAO();
		this.transDAO = factory.getTransactionDAO();
		this.patDAO = factory.getPatientDAO();
		this.loggedInMID = loggedInMID;
	}

	/**
	 * Returns the office visit bean for the office visit
	 * 
	 * @return the OfficeVisitBean of the office visit
	 * @throws iTrustException
	 */
	public OfficeVisitBean getOfficeVisit() throws iTrustException {
		OfficeVisitBean officeVisit = ovDAO.getOfficeVisit(ovID);
		return officeVisit;
	}
	
	/**
	 * Edits an existing diagnosis in the database
	 * @param diag The diagnosis bean that has been changed
	 * @throws DBException
	 */
	public void editDiagnosis(DiagnosisBean diag) throws DBException{
		ovdDAO.editDiagnosis(diag);
	}
	
	/**
	 * Edits an existing procedure in the database
	 * @param proc The procedure bean that has been changed
	 * @throws DBException
	 */
	public void editProcedure(ProcedureBean proc) throws DBException{
		ovpDAO.editProcedure(proc);
	}
	
	/**
	 * Edits an existing referral in the database
	 * @param patRef The patientReferral bean that has been changed
	 * @throws DBException
	 */
	public void editPatientReferral(PatientReferralBean patRef) throws DBException{
		patRefDAO.editReferral(patRef);
	}
	
	/**
	 * Returns a list of the lab procedures that have been done in an office visit.
	 * 
	 * @param mid the doctor the visit was with
	 * @param ovid the office visit's id
	 * @return a list of the lab procedures that were done in that visit
	 * @throws DBException
	 */

	public List<LabProcedureBean> getLabProcedures(long mid, long ovid) throws DBException {
		return lpDAO.getAllLabProceduresForDocOV(mid, ovid);
	}
	
	/**
	 * Returns a list of patient referrals that have been done in an office visit.
	 * 
	 * @return a list of patient referrals that were done in that visit
	 * @throws DBException
	 */

	public List<PatientReferralBean> getPatientReferralsForOfficeVisit() throws DBException {
		return patRefDAO.getReferralsForOfficeVisit(ovID);
	}

	/**
	 * This is a list of all hospitals, ordered by the office visit's hcp FIRST
	 * 
	 * @param hcpID
	 * @return
	 * @throws iTrustException
	 */
	public List<HospitalBean> getHospitals(long hcpID) throws iTrustException {
		List<HospitalBean> hcpsHospitals = personnelDAO.getHospitals(hcpID);
		List<HospitalBean> allHospitals = hospitalDAO.getAllHospitals();
		return combineLists(hcpsHospitals, allHospitals);
	}
	
	/**
	 * Combines two lists of hospitals
	 * 
	 * @param hcpsHospitals hospitals the HCP is assigned to
	 * @param allHospitals all hopsitals
	 * @return the combined list
	 */
	private List<HospitalBean> combineLists(List<HospitalBean> hcpsHospitals, List<HospitalBean> allHospitals) {
		for (HospitalBean hos : allHospitals) {
			if (!hcpsHospitals.contains(hos))
				hcpsHospitals.add(hos);
		}
		return hcpsHospitals;
	}

	/**
	 * Updates the office visit with information from the form passed in
	 * 
	 * @param form
	 *            information to update
	 * @return "success" or exception's message
	 * @throws FormValidationException
	 * @throws SQLException 
	 */
	public String updateInformation(EditOfficeVisitForm form) throws FormValidationException, SQLException {
		String confirm = "";
		try {
			LabProcedureBean lpbean = new LabProcedureBean();
			InstructionBean b = new InstructionBean();
			PatientReferralBean patRef =  new PatientReferralBean();
			patRef.setOfficeVisitID(ovID);
			patRef.setSenderID(loggedInMID);
			patRef.setPatientID(pid);
			if(form.getPriority() != null && !form.getPriority().equals("")){
				patRef.setPriority(Integer.parseInt(form.getPriority()));

			}
			if(form.getReferTo() != null && !form.getReferTo().equals("")){
				patRef.setReceiverID(Long.parseLong(form.getReferTo()));

			}
			patRef.setReferralDetails(form.getReferNotes());
			checkAddSubAction(OVSubAction.ADD_DIAGNOSIS, form.getAddDiagID(), ovID, null);
			checkAddSubAction(OVSubAction.ADD_PROCEDURE, form.getAddProcID(), ovID, null);
			checkAddSubAction(OVSubAction.ADD_IMMUNIZATION, form.getAddImmunizationID(), ovID, null, form.getHcpID());
			checkAddSubAction(OVSubAction.ADD_LAB_PROCEDURE, lpbean, form);
			checkAddSubAction(OVSubAction.ADD_INSTRUCTION, b, form);
			checkAddSubAction(OVSubAction.ADD_REFERRAL, patRef, form);
			prescriptAct.checkAddPrescription(form, ovID);
			checkRemoveSubAction(OVSubAction.REMOVE_DIAGNOSIS, form.getRemoveDiagID());
			checkRemoveSubAction(OVSubAction.REMOVE_LAB_PROCEDURE, form.getRemoveLabProcID());
			checkRemoveSubAction(OVSubAction.REMOVE_PROCEDURE, form.getRemoveProcID());
			checkRemoveSubAction(OVSubAction.REMOVE_IMMUNIZATION, form.getRemoveImmunizationID());
			checkRemoveSubAction(OVSubAction.REMOVE_MEDICATION, form.getRemoveMedID());
			checkRemoveSubAction(OVSubAction.REMOVE_INSTRUCTION, form.getRemoveInstructID());
			checkRemoveSubAction(OVSubAction.REMOVE_REFERRAL, form.getRemoveReferID());
			updateOv(form);
			confirm = "success";
			return confirm;
		} catch (iTrustException e) {
			e.printStackTrace();
			return e.getMessage();
		}
	}
	
	/**
	 * 
	 * Sends e-mail regarding the prescribed dangerous drug.
	 * 
	 * @param hcpID HCP the prescription is made by
	 * @param patID ID of the patient prescription is for
	 * @param problem The allergy and/or interaction that is the problem
	 * @return the sent e-mail
	 * @throws DBException
	 */	
	public Email makeEmailApp(long hcpID, String patID, String problem) throws DBException, iTrustException {
		PatientBean p = patDAO.getPatient(Long.parseLong(patID));
		String hcpName = personnelDAO.getName(hcpID);
		Email email = new Email();
		email.setFrom("no-reply@itrust.com");
		email.setToList(Arrays.asList(p.getEmail()));
		email.setSubject("HCP has prescribed you a potentially dangerous medication");
		email.setBody(String
				.format(
					"%s has prescribed a medication that you are allergic to or that has a known interaction with a drug you are currently taking. %s",
					hcpName, problem));
		return email;
	}

	/**
	 * Updates the office visit.
	 * 
	 * @param form form with all the information
	 * @throws DBException
	 * @throws FormValidationException
	 */
	private void updateOv(EditOfficeVisitForm form) throws DBException, FormValidationException {
		validator.validate(form);
		OfficeVisitBean ov = new OfficeVisitBean(ovID);
		ov.setNotes(form.getNotes());
		ov.setVisitDateStr(form.getVisitDate());
		ov.setHcpID(Long.valueOf(form.getHcpID()));
		ov.setPatientID(Long.valueOf(form.getPatientID()));
		ov.setHospitalID(form.getHospitalID());
		ovDAO.update(ov);
	}
	
	/**
	 * Adds a diagnosis or a procedure to an office visit
	 * 
	 * @param action the type of action to add
	 * @param code the CPT code of the action
	 * @param visitID the office visit to add the action to
	 * @param dateOfDeath the date of death, if needed
	 * @return true if the operation completed; false if the code was null
	 * @throws DBException
	 * @throws iTrustException
	 */	
	private boolean checkAddSubAction(OVSubAction action, String code, long visitID, String dateOfDeath)
			throws DBException, iTrustException {
		if (code == null || "".equals(code)) {
			return false;
		} else {
			switch (action) {
			case ADD_DIAGNOSIS:
				ovdDAO.addDiagnosisToOfficeVisit(Double.valueOf(code), visitID);
				break;
			case ADD_IMMUNIZATION:
			case ADD_PROCEDURE:
				ovpDAO.addProcedureToOfficeVisit(code, visitID, "");
				break;
			case ADD_LAB_PROCEDURE:
				ovlpDAO.addLabProcedureToOfficeVisit(code, visitID, pid);
				break;
			default:
				return false;
			}
			return true;
		}
	}
	
	private boolean checkAddSubAction(OVSubAction action, String code, long visitID, String dateOfDeath, String hcpid)
	throws DBException, iTrustException {
		if (code == null || "".equals(code)) {
			return false;
		} else {
			ovpDAO.addProcedureToOfficeVisit(code, visitID, hcpid);
			return true;
		}
	 }
	
	/**
	 * Adds an object, specifically an instruction object
	 * @param action -- type of action from an office visit
	 * @param bean -- instruction bean with info to edit
	 * @param form -- form of data
	 * @return true if operation completed; false if the code was null
	 * @throws DBException
	 * @throws iTrustException
	 */
	private boolean checkAddSubAction(OVSubAction action, InstructionBean bean, EditOfficeVisitForm form) throws DBException, iTrustException, FormValidationException{
		if( form.getAddInstName() == null || "".equals(form.getAddInstName()) ){
			return false;
		} else {
			bean.setDescriptName(form.getAddInstName());
			bean.setURL(form.getAddInstUrl());
			bean.setComments(form.getAddInstComment());
			bean.setOVID(ovID);
			bean.setHCPID(loggedInMID);
			bean.setPID(pid);
			new EditOfficeVisitValidator().validateInstructions(form);
			oviDAO.addInstruction(bean);
			return true;			
		}
	}
	
	/**
	* Adds an object, specifically an patient referral object
	*@param action -- type of action from an office visit
	*@param bean -- patient referral bean with info to edit
	*@param form -- form of data
	*@return true if operation completed; false if the code was null
	*@throws DBException
	*@throws iTrustException
	*@throws SQLException
	*/

	private boolean checkAddSubAction(OVSubAction action, PatientReferralBean bean, EditOfficeVisitForm form) throws DBException, iTrustException, FormValidationException, SQLException{
		if( bean.getReferralDetails() == null || "".equals(bean.getReferralDetails() )){
			return false;
		}
		else {
			//new EditOfficeVisitValidator().validateInstructions(form);
			patRefDAO.addReferral(bean);
			MessageBean mbean = new MessageBean();
			PatientBean patbean = patDAO.getPatient(pid);
			PersonnelBean sender =	personnelDAO.getPersonnel(bean.getSenderID());
			PersonnelBean receiver = personnelDAO.getPersonnel(bean.getReceiverID());
			mbean.setTo(pid);
			mbean.setFrom(loggedInMID);
			mbean.setRead(0);
			mbean.setSubject("New Referral");
			mbean.setBody("Still testing");
			//mbean.setBody(sender.getFullName()+" (Specialty: "+ sender.getSpecialty()+ ")" +"has referred" + patbean.getFullName() + " to " + receiver.getFullName()+" (Specialty: "+ receiver.getSpecialty()+ ")"
				//	+". \n" + "The Referral: " + bean.getReferralDetails() + " was sent at " + bean.getDateAdded());
			MessageAction.sendMessage(mbean);
			mbean.setBody("Still testing");
			//mbean.setBody(sender.getFullName()+" (Specialty: "+ sender.getSpecialty()+ ")" +"has referred" + patbean.getFullName() + " to " + receiver.getFullName()+" (Specialty: "+ receiver.getSpecialty()+ ")"
					//+". \n" + "The Referral: " + bean.getReferralDetails() + " was sent at " + bean.getDateAdded() + "\n"+"The priortiy is " + bean.getPriority());
			mbean.setTo(bean.getReceiverID());
			MessageAction.sendMessage(mbean);
			mbean.setTo(bean.getSenderID());
			MessageAction.sendMessage(mbean);
			return true;
		}
	}
	
	/**
	 * Add Lab Procdure bean
	 * @param action -- action
	 * @param lpbean -- bean that we add
	 * @param form -- form with info
	 * @return
	 * @throws DBException
	 */
	private boolean checkAddSubAction(OVSubAction action, LabProcedureBean lpbean, EditOfficeVisitForm form) throws DBException{
		if(form.getAddLabProcID() == null || "".equals(form.getAddLabProcID())){
			return false;
		}else{
			lpbean.setLoinc(form.getAddLabProcID());
			lpbean.setOvID(ovID);
			lpbean.setPriority(new Integer(form.getAddLabProcPriority()));
			lpbean.setLtID(new Long(form.getAddLtId()));
			lpbean.setPid(pid);
			lpbean.setCommentary("");
			lpbean.setStatus(LabProcedureBean.In_Transit);
			lpbean.setResults("");
			lpbean.setRights("ALLOWED");
			lpDAO.addLabProcedure(lpbean);
			return true;
		}
	}

		
	/**
	 * Removes an action from an office visit
	 * 
	 * @param action type of action to remove
	 * @param input id for the action to remove
	 * @return true if the operation completed; false if the code was null
	 * @throws FormValidationException 
	 * @throws SQLException 
	 * @throws iTrustException 
	 */
	private boolean checkRemoveSubAction(OVSubAction action, String input) throws iTrustException, SQLException, FormValidationException {
		if (input == null || "".equals(input))
			return false;
		long removeID;
		try {
			removeID = Long.valueOf(input);
		} catch (NumberFormatException e) {
			e.printStackTrace();
			return false;
		}
		switch (action) {
		case REMOVE_DIAGNOSIS:
			ovdDAO.removeDiagnosisFromOfficeVisit(removeID);
			break;
		case REMOVE_IMMUNIZATION:
		case REMOVE_PROCEDURE:
			ovpDAO.removeProcedureFromOfficeVisit(removeID);
			break;
		case REMOVE_MEDICATION:
			ovmDAO.removePrescription(removeID);
			break;
		case REMOVE_LAB_PROCEDURE:
			ovlpDAO.removeLabProcedureFromOfficeVisit(removeID);
			break;
		case REMOVE_INSTRUCTION:
			oviDAO.removeInstruction(removeID);
			break;
		case REMOVE_REFERRAL:	
			Long hcp2 = patRefDAO.getPatientReferral((int)removeID).getReceiverID();
			MessageBean mbean = new MessageBean();
			mbean.setTo(pid);
			mbean.setFrom(loggedInMID);
			mbean.setRead(0);
			mbean.setSubject("Cancelled Referral");
			mbean.setBody("A referral has been cancelled");
			MessageAction.sendMessage(mbean);
			mbean.setTo(hcp2);
			MessageAction.sendMessage(mbean);
			patRefDAO.removeReferral(removeID);
		break;
		}
		return true;
	}
	
	/**
	 * Used to edit/update instructions associated with officevisit
	 * @param ibean -- updated instructions
	 * @throws DBException
	 */
	public void editInstruction(InstructionBean ibean) throws DBException{
		//Validate
		oviDAO.editInstruction(ibean);
	}
	
	/**
	 * Used to get the instructions associated with office visit of id (ovID) 
	 * @param ovID -- unique id of the office visit
	 * @return List of the Instructions associated with the office visit
	 * @throws DBException
	 */	
	public List<InstructionBean> getInstructionsForOfficeVisit() throws DBException{
		return oviDAO.getInstructionForOfficeVisit(ovID);
	}
	
	/**
	 * Used to get all the lab techs to assign to a lab procedure and return a sorted list
	 * @return Sorted list of lab techs with procedure counts
	 * @throws DBException
	 */
	public List<LabTechProcedureBean> getAllLabTechs() throws DBException{
		
		//get all lab techs with procedure counts
		TreeSet<LabTechProcedureBean> techSet = lpDAO.getAllLabTechWithProcedures();
		List<LabTechProcedureBean> list = new ArrayList<LabTechProcedureBean>();
		
		//generate a list to return
		while(techSet != null && !techSet.isEmpty() )
			list.add(techSet.pollFirst());
		
		//return sorted list
		return list;
		
	}
	/**
	 * Used to update the assigned tech assigned to a lab procedure
	 * @param labProcedure The id of the lab procedure to update as a String
	 * @param techID The id of the new tech
	 * @throws DBException 
	 */
	public boolean updateAssignedTech(String labProcedure,long techID) throws DBException{
		
		long labProc = Long.parseLong(labProcedure);
		
	

		if(lpDAO.updateAssignedTech(labProc, techID) == techID){
			return true;
		}else{
			return false;
		}
	}
	
	/**
	 * Used by the HCP in the edit offioce view to complete a procedure
	 * by entering comments.
	 * @param labProcedure The ID of the procedure to complete as a String.
	 * @param comments The comments provided by the HCP pertaining to a particular procedure
	 * @return boolean Indication of wether or not the comments were successfully added
	 * @throws DBException 
	 */
	public boolean completeProcedure(String labProcedure, String comments) throws DBException{
		
		long procedure = Long.parseLong(labProcedure);
		
		if(lpDAO.completeProcedure(procedure, comments) != null){
			return true;
		}else{
			return false;
		}
	}
	
	/**
	 * for adding a new lab procedure to an office visit
	 * @param bean The lab procedure bean for the procedure to add
	 * @return boolean Indication of wether or not the procdure was successfully added
	 * @throws DBException 
	 */
	public boolean addLabProcedure(LabProcedureBean bean) throws DBException{
		if(lpDAO.addLabProcedure(bean) != 0){
			return true;
		}else{
			return false;
		}
	}
	
}
